"""
Flowable REST API 客户端
"""
import os
from typing import Dict, Any, Optional, List

import requests
from requests.auth import HTTPBasicAuth


class FlowableClient:
    """Flowable REST API 客户端"""
    
    def __init__(self, base_url: str, username: str, password: str, timeout: int = 60):
        """
        初始化 Flowable 客户端
        
        Args:
            base_url: Flowable REST API 基础 URL
            username: 认证用户名
            password: 认证密码
            timeout: 每次请求的超时时间（秒）
        """
        self.base_url = base_url.rstrip('/')
        self.auth = HTTPBasicAuth(username, password)
        self.session = requests.Session()
        self.session.auth = self.auth
        self.timeout = timeout

    # ---- Internal request helpers with unified timeout ----
    def _get(self, url: str, **kwargs):
        kwargs.setdefault('timeout', self.timeout)
        return self.session.get(url, **kwargs)

    def _post(self, url: str, **kwargs):
        kwargs.setdefault('timeout', self.timeout)
        return self.session.post(url, **kwargs)
        
    def deploy_process(
        self,
        bpmn_file_path: str,
        deployment_name: str = None,
        resource_name: Optional[str] = None
    ) -> Dict[str, Any]:
        """
        部署流程定义
        
        Args:
            bpmn_file_path: BPMN 文件路径
            deployment_name: 部署名称
            
        Returns:
            部署信息
        """
        url = f"{self.base_url}/service/repository/deployments"

        file_name = resource_name or os.path.basename(bpmn_file_path)
        if not file_name:
            file_name = "process.bpmn20.xml"
        if "." not in file_name:
            file_name = f"{file_name}.bpmn20.xml"

        with open(bpmn_file_path, 'rb') as f:
            files = {'file': (file_name, f, 'application/xml')}
            data = {}
            if deployment_name:
                data['deploymentName'] = deployment_name

            response = self._post(url, files=files, data=data)
        try:
            response.raise_for_status()
        except requests.HTTPError as exc:  # surfacing Flowable error details helps diagnose bad deployments
            detail = response.text
            raise RuntimeError(f"Flowable deployment failed: {response.status_code} {detail}") from exc
        return response.json()
    
    def start_process_instance(self, process_definition_key: str,
                               variables: Dict[str, Any] = None,
                               business_key: str = None,
                               **extra: Any) -> Dict[str, Any]:
        """
        启动流程实例
        
        Args:
            process_definition_key: 流程定义 Key
            variables: 流程变量
            business_key: 业务主键
            extra: 兼容保留参数 (例如调用者传入 started_by/startUserId 将被忽略)
            
        Returns:
            流程实例信息
        """
        url = f"{self.base_url}/service/runtime/process-instances"
        
        payload = {
            "processDefinitionKey": process_definition_key
        }
        
        if business_key:
            payload["businessKey"] = business_key

        # Flowable 实际使用认证用户作为 startUserId，这里显式忽略 started_by/startUserId 等外部传入字段
        if 'started_by' in extra or 'startUserId' in extra:
            pass  # 仅吞掉，避免误导调用方认为可强制指定启动人
            
        if variables:
            payload["variables"] = [
                {"name": k, "value": v} for k, v in variables.items()
            ]
        print("Starting process instance with payload:", payload)
        response = self._post(url, json=payload)
        response.raise_for_status()
        return response.json()
    
    def get_tasks(self, assignee: str = None, candidate_group: str = None,
                  process_instance_id: str = None) -> List[Dict[str, Any]]:
        """
        查询任务列表
        
        Args:
            assignee: 受让人
            candidate_group: 候选组
            process_instance_id: 流程实例 ID
            
        Returns:
            任务列表
        """
        url = f"{self.base_url}/service/runtime/tasks"
        
        params = {}
        if assignee:
            params['assignee'] = assignee
        if candidate_group:
            params['candidateGroup'] = candidate_group
        if process_instance_id:
            params['processInstanceId'] = process_instance_id
        
        response = self._get(url, params=params)
        response.raise_for_status()
        return response.json().get('data', [])
    
    def complete_task(self, task_id: str, variables: Dict[str, Any] = None) -> None:
        """
        完成任务
        
        Args:
            task_id: 任务 ID
            variables: 任务变量
        """
        url = f"{self.base_url}/service/runtime/tasks/{task_id}"
        
        payload = {"action": "complete"}
        if variables:
            payload["variables"] = [
                {"name": k, "value": v} for k, v in variables.items()
            ]
        
        response = self._post(url, json=payload)
        response.raise_for_status()
    
    def get_process_instance(self, process_instance_id: str) -> Dict[str, Any]:
        """
        获取流程实例详情
        
        Args:
            process_instance_id: 流程实例 ID
            
        Returns:
            流程实例信息
        """
        url = f"{self.base_url}/service/runtime/process-instances/{process_instance_id}"
        
        response = self._get(url)
        response.raise_for_status()
        return response.json()
    
    def get_task_variables(self, task_id: str) -> List[Dict[str, Any]]:
        """
        获取任务变量
        
        Args:
            task_id: 任务 ID
            
        Returns:
            变量列表
        """
        url = f"{self.base_url}/service/runtime/tasks/{task_id}/variables"
        
        response = self._get(url)
        response.raise_for_status()
        return response.json()

    # ---- New query helpers for hybrid listing ----
    def query_historic_process_instances(self, started_by: Optional[str], start: int = 0, size: int = 50,
                                         process_definition_key: Optional[str] = None,
                                         finished: Optional[bool] = None,
                                         business_key_like: Optional[str] = None,
                                         variables: Optional[List[Dict[str, Any]]] = None) -> Dict[str, Any]:
        """查询历史流程实例（含已完成/进行中）。

        Args:
            started_by: 发起人 (startUserId)
            start: 偏移
            size: 每页数量
            process_definition_key: 过滤流程定义 key
            finished: 若 True 只取已结束, False 只取未结束, None 不限制
        Returns:
            Flowable 历史实例查询响应 JSON
        """
        url = f"{self.base_url}/service/query/historic-process-instances"
        payload: Dict[str, Any] = {
            "sort": "startTime",
            "order": "desc",
            "start": start,
            "size": size
        }
        # started_by 允许为 None (用于 fallback 全量查询再本地过滤)
        if started_by:
            payload["startedBy"] = started_by
        if process_definition_key:
            payload["processDefinitionKey"] = process_definition_key
        if finished is True:
            payload["finished"] = True
        elif finished is False:
            payload["unfinished"] = True
        if business_key_like:
            payload["businessKeyLike"] = business_key_like
        if variables:
            # 期望格式: [{"name":"applicantId","value":"EMP001","operation":"equals"}, ...]
            payload["variables"] = variables
        resp = self._post(url, json=payload)
        resp.raise_for_status()
        return resp.json()

    def query_tasks(self, process_instance_ids: List[str]) -> List[Dict[str, Any]]:
        """批量查询指定流程实例的活动任务。

        Flowable 不直接提供批量 ID 查询的简单 GET；使用 POST /query/tasks。
        若实例过多可分批。
        """
        url = f"{self.base_url}/service/query/tasks"
        tasks: List[Dict[str, Any]] = []
        chunk_size = 50
        for i in range(0, len(process_instance_ids), chunk_size):
            chunk = process_instance_ids[i:i+chunk_size]
            # 使用 or 查询: Flowable 支持 processInstanceId 作为字符串，批量需使用 'processInstanceIds'
            body = {"processInstanceIds": chunk, "size": 1000}
            resp = self._post(url, json=body)
            resp.raise_for_status()
            data = resp.json().get('data', [])
            tasks.extend(data)
        return tasks

    # ---- Runtime instances (active only) ----
    def get_runtime_process_instances(self, 
                                     started_by: Optional[str] = None,
                                     start: int = 0, size: int = 50,                                     
                                     process_definition_key: Optional[str] = None) -> Dict[str, Any]:
        """获取运行中流程实例 (不含已完成)。

        Args:
            start: 偏移
            size: 数量
            process_definition_key: 过滤流程定义 key
        Returns:
            Flowable runtime process-instances 响应 JSON
        """
        params: Dict[str, Any] = {"start": start, "size": size}
        if process_definition_key:
            params["processDefinitionKey"] = process_definition_key
        if started_by:
            params["startedBy"] = started_by
        url = f"{self.base_url}/service/runtime/process-instances"
        resp = self._get(url, params=params)
        resp.raise_for_status()
        return resp.json()

    def query_runtime_process_instances(self, start: int = 0, size: int = 50,
                                        process_definition_key: Optional[str] = None,
                                        business_key_like: Optional[str] = None,
                                        variables: Optional[List[Dict[str, Any]]] = None) -> Dict[str, Any]:
        """使用 POST /query/process-instances 进行高级查询 (支持 businessKeyLike)。"""
        url = f"{self.base_url}/service/query/process-instances"
        payload: Dict[str, Any] = {
            "start": start,
            "size": size,
            "sort": "startTime",
            "order": "desc"
        }
        if process_definition_key:
            payload["processDefinitionKey"] = process_definition_key
        if business_key_like:
            payload["businessKeyLike"] = business_key_like
        if variables:
            payload["variables"] = variables
        resp = self._post(url, json=payload)
        resp.raise_for_status()
        return resp.json()

    def get_historic_activity_instances(self, process_instance_id: str) -> List[Dict[str, Any]]:
        """查询指定流程实例的历史活动节点 (含已完成与进行中)。"""
        url = f"{self.base_url}/service/history/historic-activity-instances"
        params = {"processInstanceId": process_instance_id, "size": 1000, "sort": "startTime", "order": "asc"}
        resp = self._get(url, params=params)
        resp.raise_for_status()
        return resp.json().get('data', [])

    # ---- Process definition helpers ----
    def get_process_definitions(self, key: Optional[str] = None) -> List[Dict[str, Any]]:
        """查询流程定义列表，可按 key 过滤，返回 data 数组。"""
        url = f"{self.base_url}/service/repository/process-definitions"
        params = {}
        if key:
            params['key'] = key
        resp = self._get(url, params=params)
        resp.raise_for_status()
        return resp.json().get('data', [])

    def get_process_definitions_by_deployment(self, deployment_id: str) -> List[Dict[str, Any]]:
        """按部署 ID 查询流程定义列表。"""
        url = f"{self.base_url}/service/repository/process-definitions"
        params = {'deploymentId': deployment_id}
        resp = self._get(url, params=params)
        resp.raise_for_status()
        return resp.json().get('data', [])

    def get_process_definition_xml(self, definition_id: str) -> str:
        """获取流程定义 BPMN XML 文本。"""
        url = f"{self.base_url}/service/repository/process-definitions/{definition_id}/resourcedata"
        resp = self._get(url)
        resp.raise_for_status()
        # 强制使用 UTF-8，避免中文名称乱码（部分服务器 header 可能错误导致 requests 误判）
        content = resp.content
        try:
            return content.decode('utf-8')
        except UnicodeDecodeError:
            # 回退使用原始自动检测结果
            return resp.text

    def get_process_instance_variables(self, process_instance_id: str) -> Dict[str, Any]:
        """获取流程实例变量 (返回 dict)。"""
        url = f"{self.base_url}/service/runtime/process-instances/{process_instance_id}/variables"
        resp = self._get(url)
        resp.raise_for_status()
        data = resp.json()
        # Flowable 返回列表: [{name:..., value:...}, ...]
        if isinstance(data, list):
            return {item.get('name'): item.get('value') for item in data}
        # 若返回非预期结构，直接返回原始
        return data
